﻿using System;
using NUnit.Framework;

namespace GlobalStringFormatter.Tests
{
    [TestFixture]
    public class FormatterSystemTests
    {
        [Test]
        public void TestEmptyConstructor()
        {
            var systemDouble = new FormatterSystem<double>();
            Assert.IsNotNull(systemDouble.Default);

            var systemInt = new FormatterSystem<int>();
            Assert.IsNotNull(systemInt);

            new FormatterSystem<string>();
            new FormatterSystem<System.DateTime>();
        }

        [Test]
        public void TestDefaultConstructor()
        {
            var system1 = new FormatterSystem<double>(v => (v/2).ToString());
            Assert.IsNotNull(system1.Default);

            var system2 = new FormatterSystem<System.DateTime>(v => v.AddDays(5).ToString());
            Assert.IsNotNull(system2.Default);
        }

        [Test]
        public void TestCopyConstructor()
        {
            var system1 = new FormatterSystem<long>(v => v.ToString());
            Assert.AreEqual(0, system1.GetFormatters().Count);
                
            var system2 = new FormatterSystem<long>(system1);
            Assert.AreNotEqual(system1, system2);
            Assert.AreEqual(system1.Default, system2.Default);
            Assert.AreEqual(0, system2.GetFormatters().Count);

            system2.Default = v => (v + 1).ToString();
            Assert.AreNotEqual(system1.Default, system2.Default);

            system1.Add(new Formatter<long>(v => v == 1, v => (v+1).ToString()));
            Assert.AreEqual(1, system1.GetFormatters().Count);
            Assert.AreEqual(0, system2.GetFormatters().Count);

            system2 = new FormatterSystem<long>(system1);
            Assert.AreEqual(1, system2.GetFormatters().Count);
        }

        [Test]
        public void TestAddPredicates()
        {
            var system = new FormatterSystem<int>(v => v.ToString());
            Assert.AreEqual(0, system.GetFormatters().Count);
            
            system.Add(v => v < 0, v => "0");
            system.Add(v => v >= 0, v => "5");
            Assert.AreEqual(2, system.GetFormatters().Count);

            system.Remove(0);
            system.Remove(0);
        }

        [Test]
        public void TestAddPredicatesWithPriority()
        {
            var system = new FormatterSystem<double>(v => v.ToString());

            system.Add(v => v < 0.00, v => "3", 0);
            Assert.AreEqual(1, system.GetFormatters().Count);

            system.Add(v => v > 5, v => (v % 3).ToString(), 0);
            Assert.AreEqual(2, system.GetFormatters().Count);

            system.Add(v => v > 10, v => (v % 4).ToString(), 1);
            Assert.AreEqual(3, system.GetFormatters().Count);

            // index does not exist for insertion
            Assert.Throws(typeof(ArgumentOutOfRangeException), () => system.Add(v => v > 20, v => v.ToString(), 4));
        }

        [Test]
        public void TestAddFormatter()
        {
            var system = new FormatterSystem<DateTime>(v => v.ToString());

            var formatter = new Formatter<DateTime>(v => v == DateTime.Now, v => v.ToString());
            system.Add(formatter);

            Assert.AreEqual(1, system.GetFormatters().Count);
            Assert.AreEqual(formatter, system.GetFormatters()[0]);
        }

        [Test]
        public void TestAddFormatterWithPriority()
        {
            var system = new FormatterSystem<DateTime>(v => v.ToString());

            var formatter = new Formatter<DateTime>(v => v == DateTime.Now, v => v.ToString());
            system.Add(formatter, 0);

            system.Add(new Formatter<DateTime>(v => v == DateTime.Now.ToLocalTime(), v => v.AddHours(5).ToString()), 0);
            Assert.AreEqual(2, system.GetFormatters().Count);
        }

        [Test]
        public void TestGetFormatters()
        {
            var system = new FormatterSystem<Exception>();
            system.Add(e => e.Message == string.Empty, e => e.Message);

            Assert.AreEqual(1, system.GetFormatters().Count);

            var formatter = new Formatter<Exception>(e => e.Data == null, e => e.Message);
            system.Add(formatter, 0);

            system.GetFormatters().Reverse();
            Assert.AreEqual(formatter, system.GetFormatters()[0]);

            // won't matter since GetFormatters() returns a new list
            system.GetFormatters().Add(formatter);
            Assert.AreEqual(2, system.GetFormatters().Count);
        }

        [Test]
        public void TestRemovePriority()
        {
            var system = new FormatterSystem<int>(v => v.ToString());
            system.Add(new Formatter<int>(v => v < 4, v => (v+1).ToString()));
            Assert.AreEqual(1, system.GetFormatters().Count);

            Assert.Throws(typeof (ArgumentOutOfRangeException), () => system.Remove(1));

            system.Remove(0);
            Assert.AreEqual(0, system.GetFormatters().Count);
        }

        [Test]
        public void TestRemoveFormatter()
        {
            var system = new FormatterSystem<DateTime>(v => v.ToString());

            var formatter1 = new Formatter<DateTime>(v => v == DateTime.Now, v => v.ToString());
            system.Add(formatter1, 0);

            var formatter2 = new Formatter<DateTime>(v => v == DateTime.Now.AddDays(3), v => v.ToString());
            system.Remove(formatter2);
            Assert.AreEqual(1, system.GetFormatters().Count);

            system.Remove(formatter1);
            Assert.AreEqual(0, system.GetFormatters().Count);
        }

        [Test]
        public void TestReplacePredicates()
        {
            var system = new FormatterSystem<DateTime>(v => v.ToString());

            var formatter1 = new Formatter<DateTime>(v => v == DateTime.Now, v => v.ToString());
            system.Add(formatter1, 0);

            system.Replace(0, v => v == DateTime.UtcNow, v => v.ToString());
            Assert.AreEqual(1, system.GetFormatters().Count);
        }

        [Test]
        public void TestReplaceFormatter()
        {
            var system = new FormatterSystem<int>();
            var formatter1 = new Formatter<int>(g => g > 3000, g => string.Format("{0:C}", g));
            Assert.Throws(typeof (ArgumentOutOfRangeException),
                          () => system.Replace(0, new Formatter<int>(g => g == 0, g => g.ToString())));

            system.GetFormatters().Add(formatter1);
            Assert.AreEqual(0, system.GetFormatters().Count);

            system.Add(formatter1);

            var formatter2 = new Formatter<int>(f => f < 0, f => (f + 5).ToString());
            Assert.AreEqual(1, system.GetFormatters().Count);

            system.Replace(0, formatter2);
            Assert.AreEqual(1, system.GetFormatters().Count);

            Assert.Throws(typeof(ArgumentOutOfRangeException), () => system.Replace(2, formatter1));
        }

        [Test]
        public void TestClear()
        {
            var system = new FormatterSystem<object>(v => v.ToString());
            system.Add(new Formatter<object>(v => v == null, v => string.Empty));

            Assert.AreEqual(1, system.GetFormatters().Count);

            system.Clear();
            Assert.AreEqual(0, system.GetFormatters().Count);

            Assert.NotNull(system.Default);
        }
    }
}
